// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/loader/netlog_observer.h"

#include <stddef.h>

#include "base/strings/string_util.h"
#include "base/values.h"
#include "content/browser/loader/resource_request_info_impl.h"
#include "content/public/common/resource_response.h"
#include "net/base/load_flags.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_util.h"
#include "net/log/net_log_capture_mode.h"
#include "net/log/net_log_entry.h"
#include "net/log/net_log_event_type.h"
#include "net/log/net_log_source_type.h"
#include "net/log/net_log_with_source.h"
#include "net/spdy/spdy_header_block.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_netlog_params.h"

namespace content {
const size_t kMaxNumEntries = 1000;

// static
NetLogObserver* NetLogObserver::instance_ = NULL;

// static
base::LazyInstance<std::unique_ptr<base::ThreadChecker>>::Leaky
    NetLogObserver::io_thread_checker_;

NetLogObserver::NetLogObserver() { }

NetLogObserver::~NetLogObserver() { }

NetLogObserver::ResourceInfo* NetLogObserver::GetResourceInfo(uint32_t id)
{
    RequestToInfoMap::iterator it = request_to_info_.find(id);
    if (it != request_to_info_.end())
        return it->second.get();
    return NULL;
}

void NetLogObserver::OnAddEntry(const net::NetLogEntry& entry)
{
    DCHECK(io_thread_checker_.Get().get());

    // The events that the Observer is interested in only occur on the IO thread.
    if (!io_thread_checker_.Get()->CalledOnValidThread())
        return;

    if (entry.source().type == net::NetLogSourceType::URL_REQUEST)
        OnAddURLRequestEntry(entry);
}

void NetLogObserver::OnAddURLRequestEntry(const net::NetLogEntry& entry)
{
    bool is_begin = entry.phase() == net::NetLogEventPhase::BEGIN;
    bool is_end = entry.phase() == net::NetLogEventPhase::END;

    if (entry.type() == net::NetLogEventType::URL_REQUEST_START_JOB) {
        if (is_begin) {
            if (request_to_info_.size() > kMaxNumEntries) {
                LOG(WARNING) << "The raw headers observer url request count has grown "
                                "larger than expected, resetting";
                request_to_info_.clear();
            }

            request_to_info_[entry.source().id] = new ResourceInfo();
        }
        return;
    } else if (entry.type() == net::NetLogEventType::REQUEST_ALIVE) {
        // Cleanup records based on the TYPE_REQUEST_ALIVE entry.
        if (is_end)
            request_to_info_.erase(entry.source().id);
        return;
    }

    ResourceInfo* info = GetResourceInfo(entry.source().id);
    if (!info)
        return;

    switch (entry.type()) {
    case net::NetLogEventType::HTTP_TRANSACTION_SEND_REQUEST_HEADERS: {
        std::unique_ptr<base::Value> event_params(entry.ParametersToValue());
        std::string request_line;
        net::HttpRequestHeaders request_headers;

        if (!net::HttpRequestHeaders::FromNetLogParam(
                event_params.get(), &request_headers, &request_line)) {
            NOTREACHED();
        }

        // We need to clear headers in case the same url_request is reused for
        // several http requests (e.g. see http://crbug.com/80157).
        info->request_headers.clear();

        for (net::HttpRequestHeaders::Iterator it(request_headers);
             it.GetNext();) {
            info->request_headers.push_back(std::make_pair(it.name(), it.value()));
        }
        info->request_headers_text = request_line + request_headers.ToString();
        break;
    }
    case net::NetLogEventType::HTTP_TRANSACTION_HTTP2_SEND_REQUEST_HEADERS: {
        std::unique_ptr<base::Value> event_params(entry.ParametersToValue());
        net::SpdyHeaderBlock request_headers;

        if (!net::SpdyHeaderBlockFromNetLogParam(event_params.get(),
                &request_headers)) {
            NOTREACHED();
        }

        // We need to clear headers in case the same url_request is reused for
        // several http requests (e.g. see http://crbug.com/80157).
        info->request_headers.clear();

        for (net::SpdyHeaderBlock::const_iterator it = request_headers.begin();
             it != request_headers.end(); ++it) {
            info->request_headers.push_back(
                std::make_pair(it->first.as_string(), it->second.as_string()));
        }
        info->request_headers_text = "";
        break;
    }
    case net::NetLogEventType::HTTP_TRANSACTION_READ_RESPONSE_HEADERS: {
        std::unique_ptr<base::Value> event_params(entry.ParametersToValue());

        scoped_refptr<net::HttpResponseHeaders> response_headers;

        if (!net::HttpResponseHeaders::FromNetLogParam(event_params.get(),
                &response_headers)) {
            NOTREACHED();
        }

        info->http_status_code = response_headers->response_code();
        info->http_status_text = response_headers->GetStatusText();
        std::string name, value;

        // We need to clear headers in case the same url_request is reused for
        // several http requests (e.g. see http://crbug.com/80157).
        info->response_headers.clear();

        for (size_t it = 0;
             response_headers->EnumerateHeaderLines(&it, &name, &value);) {
            info->response_headers.push_back(std::make_pair(name, value));
        }

        if (!info->request_headers_text.empty()) {
            info->response_headers_text = net::HttpUtil::ConvertHeadersBackToHTTPResponse(
                response_headers->raw_headers());
        } else {
            // SPDY request.
            info->response_headers_text = "";
        }
        break;
    }
    default:
        break;
    }
}

void NetLogObserver::Attach(net::NetLog* net_log)
{
    DCHECK(!instance_);
    io_thread_checker_.Get().reset(new base::ThreadChecker());
    if (net_log) {
        instance_ = new NetLogObserver();
        net_log->DeprecatedAddObserver(
            instance_, net::NetLogCaptureMode::IncludeCookiesAndCredentials());
    }
}

void NetLogObserver::Detach()
{
    DCHECK(io_thread_checker_.Get().get() && io_thread_checker_.Get()->CalledOnValidThread());
    io_thread_checker_.Get().reset();
    if (instance_) {
        // Safest not to do this in the destructor to maintain thread safety across
        // refactorings.
        instance_->net_log()->DeprecatedRemoveObserver(instance_);
        delete instance_;
        instance_ = NULL;
    }
}

NetLogObserver* NetLogObserver::GetInstance()
{
    if (!io_thread_checker_.Get().get())
        return nullptr;

    DCHECK(io_thread_checker_.Get()->CalledOnValidThread());

    return instance_;
}

// static
void NetLogObserver::PopulateResponseInfo(net::URLRequest* request,
    ResourceResponse* response)
{
    const ResourceRequestInfoImpl* request_info = ResourceRequestInfoImpl::ForRequest(request);
    if (!request_info || !request_info->ShouldReportRawHeaders())
        return;

    uint32_t source_id = request->net_log().source().id;
    NetLogObserver* dev_tools_net_log_observer = NetLogObserver::GetInstance();
    if (dev_tools_net_log_observer == NULL)
        return;
    response->head.devtools_info = dev_tools_net_log_observer->GetResourceInfo(source_id);
}

} // namespace content
